
#include "LoggerService.hpp"

#include <filesystem>
#include <thread>

#include "DbusManager.hpp"
#include "LogCollection.hpp"

#define TEST_FLUSH_ALL false

#define TEST_CLEAR_ALL false


using LogCollector::Severity;
using LogCollector::Logger;
using LogCollector::ReturnStatus;
using LogCollector::LOG_DIRECTORY;
using LogCollector::LOG_FILE_SUFFIX;
using LogCollector::LOG_FILE_SUBSCRIPT_FMT;
using LogCollector::LOG_FILE_BARE_FMT;
using LogCollector::TIME_CONVERSION_BASE;
using LogCollector::TIME_CONVERSION_DOUBLE;

namespace fs = std::filesystem;

static constexpr std::string_view LOG_DUMP_CMD { "sudo tar -czf" };
static constexpr std::string_view LOG_DUMP_FILE_PATH { "/var/log/shz/SS_LOG.tar.gz" };
static const std::string INTEGRATED_LOG_FILE_PATH { LOG_DIRECTORY + "allLogs"};

static constexpr int LOG_FILE_COUNT_OFFSET { -2 };

static constexpr uint64_t TEST_CYCLE_TIME { 2 * TIME_CONVERSION_DOUBLE }; //! 2 sec

LoggerService::LoggerService(
    std::string aServiceName, std::string aServicePath, std::string aServiceInterface)
: DbusContext(aServiceName, aServicePath, aServiceInterface)
{
}

bool LoggerService::registerInterfaces()
{
    sd_bus_slot* slot { nullptr };
    auto slots = getSlots();

    slot = DbusManager::registerInterface("flushAll", "", "",
        this, &LoggerService::flushAll,
        getDbus(), getServicePath().c_str(), getServiceInterface().c_str()
    );
    if (!slot) {
        return false;
    }
    slots.push_back(slot);

    slot = DbusManager::registerInterface("clearAll", "", "",
        this, &LoggerService::clearAll,
        getDbus(), getServicePath().c_str(), getServiceInterface().c_str()
    );
    if (!slot) {
        return false;
    }
    slots.push_back(slot);

    slot = DbusManager::registerInterface("dumpAll", "", "",
        this, &LoggerService::dumpAll,
        getDbus(), getServicePath().c_str(), getServiceInterface().c_str()
    );
    if (!slot) {
        return false;
    }
    slots.push_back(slot);

    slot = DbusManager::registerInterface("integrateAll", "", "",
        this, &LoggerService::integrateAll,
        getDbus(), getServicePath().c_str(), getServiceInterface().c_str()
    );
    if (!slot) {
        return false;
    }
    slots.push_back(slot);

    slot = DbusManager::registerInterface("registerClient", "s", "b",
        this, &LoggerService::registerClient,
        getDbus(), getServicePath().c_str(), getServiceInterface().c_str()
    );
    if (!slot) {
        return false;
    }
    slots.push_back(slot);

    slot = DbusManager::registerInterface("unregisterClient", "s", "b",
        this, &LoggerService::unregisterClient,
        getDbus(), getServicePath().c_str(), getServiceInterface().c_str()
    );
    if (!slot) {
        return false;
    }
    slots.push_back(slot);

    return true;
}

bool LoggerService::prepare()
{
    mJournalLoggerHandler = std::make_shared<JournalLoggerHandler>(
        JournalLoggerHandler::SERVICE_NAME,
        JournalLoggerHandler::SERVICE_PATH,
        JournalLoggerHandler::SERVICE_INTERFACE
    );

    //! 运行各自的线程上
    std::thread LocalHanlder(&JournalLoggerHandler::run, mJournalLoggerHandler.get());
    LocalHanlder.detach();

    mLocalLoggerHandler = std::make_shared<LocalLoggerHandler>(
        LocalLoggerHandler::SERVICE_NAME,
        LocalLoggerHandler::SERVICE_PATH,
        LocalLoggerHandler::SERVICE_INTERFACE
    );

    //! 运行各自的线程上
    std::thread JournalHandler(&LocalLoggerHandler::run, mLocalLoggerHandler.get());
    JournalHandler.detach();

    if (!registerInterfaces()) {
        return false;
    }

#if TEST_FLUSH_ALL
    testFlushAll();
#endif

#if TEST_CLEAR_ALL
    testClearAll();
#endif

    if (!DbusManager::emitSignal("StartUp",
        getDbus(), getServicePath().c_str(), getServiceInterface().c_str())) {
        return false;
    }

    return true;
}

bool LoggerService::getProcessName(int aPid, char* aBuffer, size_t aSize)
{
    auto path = LogCollector::format("/proc/{}/exe", aPid);
    auto len = readlink(path.c_str(), aBuffer, aSize - 1);
    if (len == -1) {
        return false;
    }
    aBuffer[len] = '\0';
    strncpy(aBuffer, strrchr(aBuffer, '/') + 1, aSize);
    return true;
}

std::chrono::system_clock::time_point LoggerService::parseTimestampFromLog(const std::string& aLine)
{
    std::tm tm = {};

    auto leftPos = aLine.find_first_of('[') + 1;
    auto rightPos = aLine.find_first_of(']') - 1;

    std::istringstream ss(aLine.substr(leftPos, rightPos - leftPos));
    ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");

    int msec;
    if (ss.peek() == '.') {
        char dot;
        ss >> dot >> msec;
    }

    auto time = std::mktime(&tm);
    auto timePoint = std::chrono::system_clock::from_time_t(time);
    timePoint += std::chrono::milliseconds(msec);

    return timePoint;
}

int LoggerService::registerClient(sd_bus_message* aMessage, void* aUserdata, sd_bus_error* aError)
{
    //! 参数检查
    const char* clientName = nullptr;
    sd_bus_message_read(aMessage, "s", &clientName);

    //! 新建死亡监听，并注册到impl
    const char *sender = sd_bus_message_get_sender(aMessage);
    sd_bus *bus = sd_bus_message_get_bus(aMessage);
    sd_bus_creds *creds = nullptr;
    sd_bus_get_name_creds(bus, sender,
                          SD_BUS_CREDS_PID,
                          &creds);
    if (!creds) {
        SS_LOG(Severity::Level::ERROR, "sd_bus_get_name_creds failed");
        return sd_bus_reply_method_return(aMessage, "b", false);
    }
    pid_t pid = 0;
    sd_bus_creds_get_pid(creds, &pid);
    sd_bus_creds_unref(creds);

    char comm[256]; 
    if (!getProcessName(pid, comm, 256)) {
        SS_LOG(Severity::Level::WARNING, "Failed to getProcessName");
    }

    if (!mLocalLoggerHandler->reigsterClient(clientName, pid, comm)) {
        sd_bus_reply_method_return(aMessage, "b", false);
    }
    
    return sd_bus_reply_method_return(aMessage, "b", true);
}

int LoggerService::unregisterClient(sd_bus_message* aMessage, void* aUserdata, sd_bus_error* aError)
{
    //! 参数检查
    const char* clientName = nullptr;
    sd_bus_message_read(aMessage, "s", &clientName);

    if (!mLocalLoggerHandler->unregisterClient(clientName)) {
        return sd_bus_reply_method_return(aMessage, "b", false);
    }

    return sd_bus_reply_method_return(aMessage, "b", true);
}

int LoggerService::flushAll(sd_bus_message* aMessage, void* aUserdata, sd_bus_error* aError)
{
    if (!DbusManager::emitSignal("flushAll",
        getDbus(), getServicePath().c_str(), getServiceInterface().c_str())) {
        return false;
    }

    mLocalLoggerHandler->flushAll();
    mJournalLoggerHandler->flushAll();

    return sd_bus_reply_method_return(aMessage, nullptr);
}

int LoggerService::clearAll(sd_bus_message* aMessage, void* aUserdata, sd_bus_error* aError)
{
    if (!DbusManager::emitSignal("clearAll",
        getDbus(), getServicePath().c_str(), getServiceInterface().c_str())) {
        return false;
    }

    mLocalLoggerHandler->clearAll();
    mJournalLoggerHandler->clearAll();

    return sd_bus_reply_method_return(aMessage, nullptr);
}

int LoggerService::dumpAll(sd_bus_message* aMessage, void* aUserdata, sd_bus_error* aError)
{
    doDumpAll();
    return sd_bus_reply_method_return(aMessage, nullptr);
}

int LoggerService::integrateAll(sd_bus_message* aMessage, void* aUserdata, sd_bus_error* aError)
{
    //! 过程
    //! 1. 需要获取到 所有进程的文件
    //! 2. 根据Y字型算法，将最早的日志放入其中
    auto processes = getAllLogUnit();

    if (!processes.empty()) {
        doIntegrateAll(processes);
    } else {
        SS_LOG_WARNING("No log stored");
    }

    return sd_bus_reply_method_return(aMessage, nullptr);
}

bool LoggerService::doDumpAll()
{
    //! 将 "/var/log/shz" 打包即可
    const auto cmd = std::string(LOG_DUMP_CMD) + " " +
        std::string(LOG_DUMP_FILE_PATH) + " " +
        std::string(LOG_DIRECTORY);
    return std::system(cmd.c_str()) != -1;
}

std::unordered_map<std::string, LoggerService::LogUnit> LoggerService::getAllLogUnit()
{
    //! 需要获取到 所有进程的文件
    std::unordered_map<std::string, LogUnit> processes;
    for (const auto& iter : fs::directory_iterator(LOG_DIRECTORY)) {
        auto path = iter.path();
        if (!iter.is_regular_file() || path.extension() != LOG_FILE_SUFFIX) {
            continue;
        }

        auto splited = LogCollector::__split(path.filename().stem(), '_');
        if (splited.empty()) {
            continue;
        }

        auto process = splited.front();
        if (processes.find(process) != processes.end()) {
            continue;
        }

        int curCount = LOG_FILE_COUNT_OFFSET;
        std::string filePath;
        //! 检查文件是否存在
        do {
            ++curCount;
            if (curCount >= 0) {
                filePath = LogCollector::format(
                    LOG_FILE_SUBSCRIPT_FMT, process, curCount + 1);
            } else {
                filePath = LogCollector::format(
                    LOG_FILE_BARE_FMT, process);
            }
        } while(fs::exists(filePath));

        if (curCount < 0) {
            continue;
        }
        LogUnit unit {
            .mCurCount = curCount
        };
        processes[process] = std::move(unit);
    }

    return processes;
}

void LoggerService::openNextFileOfUnit(const std::string& aName, LogUnit& aUnit)
{
    std::string filePath;
    if (aUnit.mCurCount != 0) {
        filePath = LogCollector::format(
            LOG_FILE_SUBSCRIPT_FMT, aName, aUnit.mCurCount);
    } else {
        filePath = LogCollector::format(
            LOG_FILE_BARE_FMT, aName);
    }

    if (aUnit.mFile.is_open()) {
        aUnit.mFile.close();
    }
    aUnit.mFile.clear();
    aUnit.mFile.open(filePath);
    aUnit.mCurCount --;
}

void LoggerService::doIntegrateAll(std::unordered_map<std::string, LogUnit>& aProcesses)
{
    //! 根据Y字型算法，将最早的日志放入其中
    for (auto& process : aProcesses) {
        openNextFileOfUnit(process.first, process.second);
    }

    //! 创建日志整体的日志输出
    std::ofstream output(INTEGRATED_LOG_FILE_PATH);
    while (!aProcesses.empty()) {
        //! 从每个里面取出一个，然后比大小
        std::string oldestProcess = aProcesses.begin()->first;
        auto process = aProcesses.begin();
        while (process != aProcesses.end()) { 
            auto& unit = process->second;
            if (unit.mLine.empty()) {
                std::getline(unit.mFile, unit.mLine);
            }

            while(unit.mLine.empty() && unit.mCurCount >= 0) {
                openNextFileOfUnit(process->first, unit);
                std::getline(unit.mFile, unit.mLine);
            };

            if (unit.mLine.empty() && unit.mCurCount < 0) {
                process = aProcesses.erase(process);
                continue;
            }

            auto oldest = aProcesses.find(oldestProcess);
            if (oldest != aProcesses.end() && oldest != process) {
                auto oldTime = parseTimestampFromLog(oldest->second.mLine);
                auto curTime = parseTimestampFromLog(unit.mLine);
                if (curTime < oldTime) {
                    oldestProcess = process->first;
                }
            }

            ++process;            
        }

        process = aProcesses.find(oldestProcess);
        if (process != aProcesses.end()) {
            output << process->second.mLine + "\n";
            process->second.mLine.clear();
        }
    }

    output.flush();
}

void LoggerService::testClearAll()
{
    //! for test clearAll
    sd_event_add_time_relative(
        getEvent(),
        &mSource,
        CLOCK_MONOTONIC,
        TEST_CYCLE_TIME,
        0,
        [](sd_event_source *aSource, uint64_t aUsec, void *aUserdata){
            LoggerService* service = static_cast<LoggerService*>(aUserdata);
            if (!DbusManager::emitSignal("clearAll", service->getDbus(),
                service->getServicePath().c_str(), service->getServiceInterface().c_str())) {
                return 0;
            }
            service->mLocalLoggerHandler->clearAll();
            service->mJournalLoggerHandler->clearAll();
            
            //! 重设定时器
            sd_event_source_set_time_relative(aSource, TEST_CYCLE_TIME);

            //! 重新启用（关键）
            sd_event_source_set_enabled(aSource, SD_EVENT_ON);
            return 0;
        },
        this
    );
}

void LoggerService::testFlushAll()
{
    //! for test clearAll
    sd_event_add_time_relative(
        getEvent(),
        &mSource,
        CLOCK_MONOTONIC,
        TEST_CYCLE_TIME,      // 2 秒
        0,
        [](sd_event_source *aSource, uint64_t aUsec, void *aUserdata){
            LoggerService* service = static_cast<LoggerService*>(aUserdata);
            if (!DbusManager::emitSignal("flushAll", service->getDbus(),
                service->getServicePath().c_str(), service->getServiceInterface().c_str())) {
                return 0;
            }

            service->mLocalLoggerHandler->flushAll();
            service->mJournalLoggerHandler->flushAll();
            
            //! 重设定时器
            sd_event_source_set_time_relative(aSource, TEST_CYCLE_TIME);

            //! 重新启用（关键）
            sd_event_source_set_enabled(aSource, SD_EVENT_ON);
            return 0;
        },
        this
    );
}
